xtask\tasks\fmt\house_rules/
cfg_target_arch.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4use anyhow::anyhow;
5use fs_err::File;
6use std::io::BufRead;
7use std::io::BufReader;
8use std::path::Path;
9
10const SUPPRESS: &str = "xtask-fmt allow-target-arch";
11
12/// Using `target_arch` in order to execute CPU-specific intrinsics
13const SUPPRESS_REASON_CPU_INTRINSIC: &str = "cpu-intrinsic";
14/// Using `target_arch` in order to implement a '*-sys'-like crate (where the
15/// structure changes depending on the host-arch)
16const SUPPRESS_REASON_SYS_CRATE: &str = "sys-crate";
17/// One off - support for the auto-arch selection logic in
18/// `build_rs_guest_arch`.
19const SUPPRESS_REASON_ONEOFF_GUEST_ARCH_IMPL: &str = "oneoff-guest-arch-impl";
20/// One off - considiton to check that `virt_hvf` is being used when both guest
21/// and host arch to be the same.
22const SUPPRESS_REASON_ONEOFF_VIRT_HVF: &str = "oneoff-virt-hvf";
23/// One off - used as part of flowey CI infra
24const SUPPRESS_REASON_ONEOFF_FLOWEY: &str = "oneoff-flowey";
25/// One off - used by petri to select native test dependencies
26const SUPPRESS_REASON_ONEOFF_PETRI_NATIVE_TEST_DEPS: &str = "oneoff-petri-native-test-deps";
27/// Onee off - used by petri to return the host architecture for test filtering
28const SUPPRESS_REASON_ONEOFF_PETRI_HOST_ARCH: &str = "oneoff-petri-host-arch";
29
30fn has_suppress(s: &str) -> bool {
31    let Some((_, after)) = s.split_once(SUPPRESS) else {
32        return false;
33    };
34
35    let after = after.trim();
36    let justification = after.split(' ').next().unwrap();
37
38    let ok = matches!(
39        justification,
40        SUPPRESS_REASON_CPU_INTRINSIC
41            | SUPPRESS_REASON_SYS_CRATE
42            | SUPPRESS_REASON_ONEOFF_GUEST_ARCH_IMPL
43            | SUPPRESS_REASON_ONEOFF_VIRT_HVF
44            | SUPPRESS_REASON_ONEOFF_FLOWEY
45            | SUPPRESS_REASON_ONEOFF_PETRI_NATIVE_TEST_DEPS
46            | SUPPRESS_REASON_ONEOFF_PETRI_HOST_ARCH
47    );
48
49    if !ok {
50        log::error!(
51            "invalid justification '{}' (must be one of [sys-crate, cpu-intrinsic]",
52            after.split(' ').next().unwrap()
53        );
54    }
55
56    ok
57}
58
59pub fn check_cfg_target_arch(path: &Path, _fix: bool) -> anyhow::Result<()> {
60    let ext = path
61        .extension()
62        .and_then(|e| e.to_str())
63        .unwrap_or_default();
64
65    if !matches!(ext, "rs") {
66        return Ok(());
67    }
68
69    // need to exclude self (and house_rules.rs, which includes help-text) from
70    // the lint
71    if path == Path::new(file!()) || path == Path::new(super::PATH_TO_HOUSE_RULES_RS) {
72        return Ok(());
73    }
74
75    // guest_test_uefi is a guest-side crate (the code runs in the guest), so
76    // target_arch here is actually referring to the guest_arch
77    //
78    // openhcl_boot uses target_arch liberally, since it runs in VTL2 entirely
79    // in-service to the VTL2 linux kernel, which will always be native-arch.
80    // Similar for the sidecar kernel and TMKs. And minimal_rt provides the
81    // (arch-specific) runtime for both of them.
82    //
83    // safe_intrinsics performs architecture-specific operations that require
84    // the use of target_arch
85    //
86    // the whp/kvm crates are inherently arch-specific, as they contain
87    // low-level bindings to a particular platform's virtualization APIs
88    //
89    // The TMK-related crates run in the guest and are inherently arch-specific.
90    if path.starts_with("guest_test_uefi")
91        || path.starts_with("openhcl/openhcl_boot")
92        || path.starts_with("openhcl/minimal_rt")
93        || path.starts_with("openhcl/sidecar")
94        || path.starts_with("support/safe_intrinsics")
95        || path.starts_with("tmk/simple_tmk")
96        || path.starts_with("tmk/tmk_core")
97        || path.starts_with("vm/whp")
98        || path.starts_with("vm/kvm")
99    {
100        return Ok(());
101    }
102
103    let mut error = false;
104
105    // TODO: this lint really ought to be a dynlint / clippy lint
106    let f = BufReader::new(File::open(path)?);
107    let mut prev_line = String::new();
108    for (i, line) in f.lines().enumerate() {
109        let line = line?;
110        if line.contains("target_arch =") || line.contains("CARGO_CFG_TARGET_ARCH") {
111            // check if current line contains valid suppress, or is commented out
112            if !line.trim().starts_with("//") && !has_suppress(&line) && !has_suppress(&prev_line) {
113                error = true;
114                log::error!(
115                    "unjustified `cfg(target_arch = ...)`: {}:{}",
116                    path.display(),
117                    i + 1
118                );
119            }
120        }
121        prev_line = line;
122    }
123
124    if error {
125        Err(anyhow!(
126            "found unjustified uses of `cfg(target_arch = ...)` in {}",
127            path.display()
128        ))
129    } else {
130        Ok(())
131    }
132}